package org.x.note.protocol;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * [文件名称]<br>
 * HttpUtil <br>
 * <br>
 * [文件描述]<br>
 * HttpClient(DefaultHttpClient)代表了一个会话，在同一个会话中，HttpClient对cookie自动进行管理(当然，
 * 也可以在程序中进行控制)。<br>
 * 在同一个会话中，当使用post或是get发起一个新的请求时，一般需要对调用前一个会话的abort()方法，否则会抛出异常。<br>
 * 有些网站登录成功后会重定向(302,303)，比如test的人人网。如果发出的是post请求，需要从响应头中取出location，并再次向网站发送请求，
 * 以获取最终数据。<br>
 * 抓取程序不要运行地过于频繁，大部分站点都有抵制刷网站机制。人人网访问过于频繁会锁账号。<br>
 * 使用录制工具录制出登录时向网站发出的请求参数。在这里，我使用了badboy，导出成jmeter文件，
 * 在jmeter中就可以看到登录时向网站发送的参数列表和相应的值。<br>
 * <br>
 * [修改记录]<br>
 * 2012-6-1 ver1.00 创建 xyl<br>
 * 
 * @author xyl
 * @version 1.00
 */
public class HttpUtil {

	/**
	 * log
	 */
	private static final Logger logger = LoggerFactory
			.getLogger(HttpUtil.class);

	private HttpClient httpClient = new DefaultHttpClient();

	/**
	 * 在同一个HttpUtil里使用同一个请求对象
	 */
	private HttpUriRequest request;

	/**
	 * 在同一个HttpUtil里使用同一个响应对象
	 */
	private HttpResponse response;

	/**
	 * 获取当前请求对象
	 * 
	 * @return
	 */
	public HttpUriRequest getRequest() {
		return request;
	}

	/**
	 * 获取当前响应对象
	 * 
	 * @return
	 */
	public HttpResponse getResponse() {
		return response;
	}

	/**
	 * 通过http的GET方式访问远程地址
	 * 
	 * @param url
	 *            访问地址
	 * @param charset
	 *            字符集
	 * @return 网页信息
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public String get(String url, Charset charset)
			throws ClientProtocolException, IOException {
		String webMessage = "";
		request = new HttpGet(url);
		try {
			logger.info("请求信息：{}", request.getRequestLine());
			response = httpClient.execute(request);

			logger.info("响应信息：{}", response.getStatusLine());
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				HttpEntity entity = response.getEntity();
				webMessage = EntityUtils.toString(entity, charset);
			}
		} finally {
			request.abort();
		}
		return webMessage;
	}

	/**
	 * 通过http的POST方式访问远程地址
	 * 
	 * @param url
	 *            访问地址
	 * @param charset
	 *            字符集
	 * @param formParams
	 *            参数list
	 * @return 网页信息
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public String post(String url, Charset charset,
			List<NameValuePair> formParams) throws ClientProtocolException,
			IOException {
		String webMessage = "";
		request = new HttpPost(url);
		try {
			HttpPost post = (HttpPost) request;
			if (formParams != null && formParams.size() != 0) {
				HttpEntity entity = new UrlEncodedFormEntity(formParams,
						charset);
				post.setEntity(entity);
			}
			logger.info("请求信息：{}", post.getRequestLine());
			response = httpClient.execute(post);

			logger.info("响应信息：{}", response.getStatusLine());
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				HttpEntity entity = response.getEntity();
				webMessage = EntityUtils.toString(entity, charset);
			}
		} finally {
			request.abort();
		}
		return webMessage;
	}

	/**
	 * Post方式提交,忽略URL中包含的参数,解决SSL双向数字证书认证<br>
	 * 需要先把访问证书导入到该密钥存储库路径里面<br>
	 * <code>keytool -import -keystore cacerts -storepass changeit -alias abc -file c:\abc.cer</code>
	 * 
	 * @param url
	 *            提交地址
	 * @param formParams
	 *            提交参数集, 键/值对
	 * @param charset
	 *            参数编码集
	 * @param keystoreUrl
	 *            密钥存储库路径
	 * @param keystorePassword
	 *            密钥存储库访问密码
	 * @return 响应消息
	 * @throws KeyStoreException
	 * @throws IOException
	 * @throws CertificateException
	 * @throws NoSuchAlgorithmException
	 * @throws UnrecoverableKeyException
	 * @throws KeyManagementException
	 * @throws UnsupportedEncodingException
	 */
	public String postSSL(String url, Charset charset,
			List<NameValuePair> formParams, URI keystoreUrl,
			String keystorePassword) throws KeyStoreException,
			NoSuchAlgorithmException, CertificateException, IOException,
			KeyManagementException, UnrecoverableKeyException {

		loadKeyStore(keystoreUrl, keystorePassword);

		return post(url, charset, formParams);
	}

	/**
	 * 加载SSL证书库
	 * 
	 * @param keystoreUrl
	 *            密钥存储库路径
	 * @param keystorePassword
	 *            密钥存储库访问密码
	 * @throws KeyStoreException
	 * @throws NoSuchAlgorithmException
	 * @throws CertificateException
	 * @throws IOException
	 * @throws KeyManagementException
	 * @throws UnrecoverableKeyException
	 */
	@SuppressWarnings("deprecation")
	private void loadKeyStore(URI keystoreUrl, String keystorePassword)
			throws KeyStoreException, NoSuchAlgorithmException,
			CertificateException, IOException, KeyManagementException,
			UnrecoverableKeyException {
		KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
		InputStream input = null;
		try {
			input = new FileInputStream(new File(keystoreUrl));
			trustStore.load(input, keystorePassword.toCharArray());
		} finally {
			input.close();
		}
		SSLSocketFactory socketFactory = new SSLSocketFactory(trustStore,
				keystorePassword, trustStore);
		// 跳过hostname验证
		socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
		Scheme sch = new Scheme("https", 443, socketFactory);
		httpClient.getConnectionManager().getSchemeRegistry().register(sch);

		logger.info("证书库[{}]已加载", keystoreUrl);
	}
}
